package de.hpi.generator.config;

import de.hpi.generationmanager.tesma.data.TesmaDataTable;
import de.hpi.generator.generator.StartArgs;
import de.hpi.generator.model.Column;
import de.hpi.generator.model.Database;
import de.hpi.generator.model.IntegerColumn;
import de.hpi.generator.model.StringColumn;
import de.hpi.generator.model.Table;
import de.hpi.generator.model.TesmaColumn;
import de.hpi.generator.model.dependencies.CompositeInclusionDependency;
import de.hpi.generator.model.dependencies.UnaryInclusionDependency;
import java.io.File;

public class ConfigParser {

    public static Database parseConfigFile(StartArgs aStartArgs) throws Exception {
        DatabaseModel aDatabaseModel = JSONConfigParser.readConfigFile(aStartArgs.getConfigPath());
        return parseDatabaseModel(aStartArgs, aDatabaseModel);
    }

    public static Database parseDatabaseModel(StartArgs aStartArgs, DatabaseModel aDatabaseModel) throws Exception {
        Database result = createDatabase(aDatabaseModel, aStartArgs.getOutputPath());
        injectTesmaDataIntoDatabase(result);
        distributeInclusionDependencyToColumns(result, aDatabaseModel.InclusionDependencies.getNormalizedInclusionDependencyModels());
        ConfigValidator.findDependencyLoop(result);
        return result;
    }

    /**
     * Creates a Database from a Database Configuration Model
     * 
     * @param aDatabaseModel
     * @return a database object filled with tables and columns
     * @throws Exception 
     */
    protected static Database createDatabase(DatabaseModel aDatabaseModel, String aOutputPath) throws Exception {
        Database result = new Database(aDatabaseModel, aOutputPath);

        for (TableModel aTableModel : aDatabaseModel.tables) {
            result.getTables().add(makeTableFromTableModel(aTableModel, result));
        }

        return result;
    }

    /**
     * Distributes Inclusion Dependencies to column objects
     * 
     * @param aInclusionDependencyModels
     * @param aDatabase
     * @throws NotExistsException
     *             if columns name got from Inclusion Dependency String does not
     *             exists
     * @throws UserInputException
     */
    protected static void distributeInclusionDependencyToColumns(Database aDatabase, DependencyModels aInclusionDependencyModels) throws NotExistsException, UserInputException, Exception {
        for (DependencyModel aInclusionDependencyModel : aInclusionDependencyModels) {
            if (aInclusionDependencyModel.isComposite()) {
                distributeCompositeInclusionDependencyToColumns(aDatabase, aInclusionDependencyModel);
            } else {
                distributeUnaryInclusionDependencyToColumns(aDatabase, aInclusionDependencyModel);
            }
        }

    }

    /**
     * Manages composite inclusion dependencies
     * 
     * @param aDatabase
     * @param aInclusionDependencyModel
     * @return CompositeInclusionDependency
     * @throws NotExistsException
     * @throws UserInputException
     */
    protected static void distributeCompositeInclusionDependencyToColumns(Database aDatabase, DependencyModel aInclusionDependencyModel) throws NotExistsException, UserInputException, Exception {
        String[] aReferencedColumnNames = aInclusionDependencyModel.referenced.split(",");
        CompositeInclusionDependency aCompositeInclusionDependency = new CompositeInclusionDependency(aDatabase, aInclusionDependencyModel);

        for (String aColumnName : aReferencedColumnNames) {
            Column aReferencedColumn = aDatabase.findColumnByPath(aColumnName);
            aReferencedColumn.getInclusionDependencyCollection().add(aCompositeInclusionDependency);
        }
    }

    /**
     * Manages unary inclusion dependencies
     * 
     * @param aDatabase
     * @param aInclusionDependencyModel
     * @throws NotExistsException
     */
    protected static void distributeUnaryInclusionDependencyToColumns(Database aDatabase, DependencyModel aInclusionDependencyModel) throws Exception {
        Column aReferencedColumn = aDatabase.findColumnByPath(aInclusionDependencyModel.referenced);
        Column aDependentColumn = aDatabase.findColumnByPath(aInclusionDependencyModel.dependent);

        aReferencedColumn.getInclusionDependencyCollection().add(new UnaryInclusionDependency(aDependentColumn, aReferencedColumn));
    }

    /**
     * Makes a Table from a table model
     * @param aTableModel
     * @param aOwner a database to which the table should belong
     * @return a table
     * @throws Exception
     */
    protected static Table makeTableFromTableModel(TableModel aTableModel, Database aOwner) throws Exception {
        Table result = new Table(aTableModel, aOwner);

        for (ColumnModel aColumnModel : aTableModel.columns) {
            result.getColumns().add(makeColumnFromColumnModel(aColumnModel, result));
        }
        return result;
    }

    public static Column makeColumnFromColumnModel(ColumnModel aColumnModel, Table aOwner) throws Exception {
        Column result = null;
        if (aColumnModel.dataType.compareToIgnoreCase("String") == 0) {
            result = new StringColumn(aColumnModel, aOwner);
        } else if (aColumnModel.dataType.compareToIgnoreCase("Integer") == 0) {
            result = new IntegerColumn(aColumnModel, aOwner);
        } else if (aColumnModel.dataType.compareToIgnoreCase("Datetime") == 0) {
            throw new Exception("Must be implemented");
        }

        return result;
    }

    protected static void injectTesmaDataIntoDatabase(Database aDatabase) throws Exception {
        File workingDirectory = new File(aDatabase.getWorkingDirectoryPath());

        for (File aTableFolder : workingDirectory.listFiles()) {
            String aTableFolderName = aTableFolder.getName();

            if (aTableFolderName.endsWith(TesmaDataTable.NAME_SUFFIX)) {
                aDatabase.getTables().add(createTesmaTable(aTableFolderName, aDatabase));
            }
        }
    }

    private static Table createTesmaTable(String aTableName, Database aDatabase) throws Exception {
        Table result = new Table(new TableModel(aTableName, 0), aDatabase);
        File workingDirectory = new File(result.getWorkingDirectoryPath());

        for (File aColumnFolder : workingDirectory.listFiles()) {
            result.getColumns().add(createTesmaColumn(aColumnFolder.getName(), result));
        }

        return result;
    }

    private static TesmaColumn createTesmaColumn(String aColumnName, Table aOwner) throws Exception {
        TesmaColumn result = new TesmaColumn(new ColumnModel(aColumnName, 0, TesmaColumn.TYPE_NAME, new DistributionModelZero()), aOwner);
        return result;
    }
}
